import { UsePlugin, GetProps, GetState } from '../utils/usePlugin';
import { ConsolePlugin } from '../plugins/Plugin';
import { ConsoleController } from './ConsoleController';
import { PluginManager } from './PluginManager';

export interface ConsoleCommanderOptions<T extends UsePlugin, P> {
  plugins?: T;
  prefix?: string;
  commands(this: P): void;
}

type ConsoleCommander<ExtraProps = {}, ExtraState = {}> = ConsoleCommanderFunc<ExtraProps, ExtraState> & {
  global: ConsoleCommanderGlobal<ExtraProps, ExtraState>;
  local: ConsoleCommanderLocal<ExtraProps, ExtraState>;
};

type ConsoleCommanderFunc<ExtraProps = {}, ExtraState = {}> = {
  <
    T extends UsePlugin,
    P = ConsoleController<GetProps<T> & ExtraProps, GetState<T> & ExtraState>
  >(options: ConsoleCommanderOptions<T, P>): ConsoleController;
}

type ConsoleCommanderGlobal<ExtraProps = {}, ExtraState = {}> = <Props, State>(plugins: UsePlugin<Props, State> | ConsolePlugin<Props, State>) => ConsoleCommander<Props & ExtraProps, State & ExtraState>;

type ConsoleCommanderLocal<ExtraProps = {}, ExtraState = {}> = <Props, State>(plugins: UsePlugin<Props, State> | ConsolePlugin<Props, State>) => ConsoleCommanderFunc<Props & ExtraProps, State & ExtraState> & {
  local: ConsoleCommanderLocal<Props & ExtraProps, State & ExtraState>;
};

export const baseConsoleCommander: ConsoleCommander = function (this: PluginManager | null, options) {
  const manager = this instanceof PluginManager
    ? this.clone()
    : new PluginManager();
  const router = new ConsoleController(manager);

  manager.appendLocalPlugins(options.plugins);
  router.setPrefix(options.prefix);
  router.setActions(options.commands);

  return router;
}

baseConsoleCommander.global = function (this: PluginManager | null, plugins) {
  const manager = this instanceof PluginManager
    ? this.clone()
    : new PluginManager();
  const consoleCommander = baseConsoleCommander.bind(manager);

  manager.appendGlobalPlugins(plugins);
  consoleCommander.global = baseConsoleCommander.global.bind(manager);
  consoleCommander.local = baseConsoleCommander.local.bind(manager);

  return consoleCommander;
};

baseConsoleCommander.local = function (this: PluginManager | null, plugins) {
  const manager = this instanceof PluginManager
    ? this.clone()
    : new PluginManager();
  const consoleCommander = baseConsoleCommander.bind(manager);

  manager.appendLocalPlugins(plugins);
  consoleCommander.local = baseConsoleCommander.local.bind(manager);

  return consoleCommander;
};
